﻿using MaterialDesignThemes.Wpf;
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using YoutubeDl.Wpf.Utils;

namespace YoutubeDl.Wpf.Models;

public class Settings
{
    /// <summary>
    /// Defines the default configuration version
    /// used by this version of the app.
    /// </summary>
    public const int DefaultVersion = 1;

    /// <summary>
    /// Defines the default width of the main window.
    /// </summary>
    public const double DefaultWindowWidth = 904.0;

    /// <summary>
    /// Defines the default height of the main window.
    /// </summary>
    public const double DefaultWindowHeight = 720.0;

    /// <summary>
    /// Defines the default limit on the number of log messages
    /// displayed in the logs view.
    /// </summary>
    public const int DefaultLoggingMaxEntries = 1024;

    /// <summary>
    /// Defines the default custom output filename template.
    /// We use yt-dlp's default as the default custom value.
    /// </summary>
    public const string DefaultCustomOutputTemplate = "%(title)s [%(id)s].%(ext)s";

    /// <summary>
    /// Gets or sets the settings version number.
    /// Defaults to <see cref="DefaultVersion"/>.
    /// </summary>
    public int Version { get; set; } = DefaultVersion;

    public double WindowWidth { get; set; } = DefaultWindowWidth;

    public double WindowHeight { get; set; } = DefaultWindowHeight;

    public BaseTheme AppColorMode { get; set; } = BaseTheme.Inherit;

    public BackendTypes Backend { get; set; } = BackendTypes.Ytdlp;

    public string BackendPath { get; set; } = "";

    public BackendArgument[] BackendGlobalArguments { get; set; } = [];

    public BackendArgument[] BackendDownloadArguments { get; set; } = [];

    public bool BackendAutoUpdate { get; set; } = true;

    public DateTimeOffset BackendLastUpdateCheck { get; set; }

    public string FfmpegPath { get; set; } = "";

    public string Proxy { get; set; } = "";

    public int LoggingMaxEntries { get; set; } = DefaultLoggingMaxEntries;

    public Preset SelectedPreset { get; set; } = Preset.Auto;

    public Preset[] CustomPresets { get; set; } = [];

    public bool AddMetadata { get; set; } = true;

    public bool DownloadThumbnail { get; set; } = true;

    public bool DownloadSubtitles { get; set; } = true;

    public bool DownloadSubtitlesAllLanguages { get; set; }

    public bool DownloadAutoGeneratedSubtitles { get; set; }

    public bool DownloadPlaylist { get; set; }

    public bool UseCustomOutputTemplate { get; set; }

    public string CustomOutputTemplate { get; set; } = DefaultCustomOutputTemplate;

    public string[] OutputTemplateHistory { get; set; } = [];

    public bool UseCustomPath { get; set; }

    public string DownloadPath { get; set; } = "";

    public string[] DownloadPathHistory { get; set; } = [];

    /// <summary>
    /// Loads settings from Settings.json.
    /// </summary>
    /// <param name="cancellationToken">A token that may be used to cancel the read operation.</param>
    /// <returns>The <see cref="Settings"/> object.</returns>
    public static async Task<Settings> LoadAsync(CancellationToken cancellationToken = default)
    {
        var settings = await FileHelper.LoadFromJsonFileAsync("Settings.json", SettingsJsonSerializerContext.Default.Settings, cancellationToken).ConfigureAwait(false);
        settings.Update();
        settings.Validate();
        return settings;
    }

    /// <summary>
    /// Saves settings to Settings.json.
    /// </summary>
    /// <param name="cancellationToken">A token that may be used to cancel the write operation.</param>
    /// <returns>A task that represents the asynchronous write operation.</returns>
    public Task SaveAsync(CancellationToken cancellationToken = default)
        => FileHelper.SaveToJsonFileAsync("Settings.json", this, SettingsJsonSerializerContext.Default.Settings, cancellationToken);

    /// <summary>
    /// Updates settings to the latest version.
    /// </summary>
    /// <exception cref="NotSupportedException">
    /// The version number is not supported.
    /// </exception>
    private void Update()
    {
        switch (Version)
        {
            case 0:
                Version++;
                goto case 1;
            case DefaultVersion:
                return;
            default:
                throw new NotSupportedException($"Settings version {Version} is not supported.");
        }
    }

    /// <summary>
    /// Validates that all properties have valid values.
    /// </summary>
    /// <exception cref="InvalidEnumArgumentException">
    /// One of the enum properties have an invalid value.
    /// </exception>
    /// <exception cref="ArgumentOutOfRangeException">
    /// One of the integer values is out of the allowed range.
    /// </exception>
    private void Validate()
    {
        switch (AppColorMode)
        {
            case BaseTheme.Inherit:
            case BaseTheme.Light:
            case BaseTheme.Dark:
                break;
            default:
                throw new InvalidEnumArgumentException(nameof(AppColorMode), (int)AppColorMode, typeof(BaseTheme));
        }

        switch (Backend)
        {
            case BackendTypes.None:
            case BackendTypes.Ytdl:
            case BackendTypes.Ytdlp:
                break;
            default:
                throw new InvalidEnumArgumentException(nameof(Backend), (int)Backend, typeof(BackendTypes));
        }

        if (LoggingMaxEntries <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(LoggingMaxEntries), LoggingMaxEntries, "Max log entries must be positive.");
        }
    }
}
